/**************************************************************************
 *
 * Copyright (C) 2007 University of California, Los Angeles.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 *************************************************************************/

/**
 * 
 */
package edu.ucla.cs.typecast.test;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import edu.ucla.cs.typecast.net.EndPoint;
import edu.ucla.cs.typecast.rmi.TypeCastServer;

/**
 *
 * @date Oct 22, 2007
 */
public class AsyncPingAckServer implements PingAckQ4 {
	private static final Object logLock = new Object(); 

	private Map<SessionKey, SessionRecord> sessionStats = Collections.synchronizedMap(new LinkedHashMap<SessionKey, SessionRecord>()); 
	private Object[] locks; 
	private EndPoint serviceEndPoint;
	private long delay; 
	private long duration; 
	private OutputStream ostream; 
	private TypeCastServer server; 
	private Timer timer = new Timer(); 
	private boolean changed = false; 
	private Class targetType; 
	private String targetDisplayType; 

	public AsyncPingAckServer(Class targetType, EndPoint endPoint, long delay, long duration, OutputStream ostream) throws IOException { 
		this.targetType = targetType; 
		targetDisplayType = getDisplayName(targetType); 
		locks = new Object[16]; 
		for(int i=0; i<locks.length; i++) { 
			locks[i] = new Object(); 
		}
		this.delay = delay; 
		this.duration = duration; 
		this.serviceEndPoint = endPoint;
		this.ostream = ostream; 
		startTimer(); 
	}
	
	private void startTimer() { 
		try { 
			TimerTask startTask = new TimerTask() { 
				public void run() { 
					init(); 
				}
			}; 
			
			timer.schedule(startTask, delay); 
			if (duration > 0) { 
				TimerTask stopTask = new TimerTask() { 
					public void run() { 
						try { 
							timer.cancel(); 
							shutdown(); 
						} catch(Throwable t) { 
							t.printStackTrace(); 
						}
						try { 
							PrintStream logStream = new PrintStream(ostream); 
							boolean needToSave; 
							synchronized(this) { 
								needToSave = changed; 
								changed = false; 
							}
							if (needToSave) { 
								saveStatistics(logStream); 
							}
						} catch(Throwable t) { 
							t.printStackTrace(); 
						}
					}
				}; 
				timer.schedule(stopTask, delay + duration); 
			}
		} catch(Throwable t) { 
			t.printStackTrace(); 
		}
	}
	
	private void init() { 
		try { 
			server = new TypeCastServer(serviceEndPoint); 
			server.export(targetType, this);			
		} catch(Throwable t) { 
			t.printStackTrace(); 
		}
	}
	
	public void shutdown() { 
		try { 
			server.unexport(this); 
		} catch(Throwable t) { 
			t.printStackTrace(); 
		}
	}

	public void pingAck(InetAddress source, InetAddress server, String sessionID, long seqno) { 
		SessionKey key = new SessionKey(source, sessionID); 
		SessionRecord session = sessionStats.get(key); 
		if (session == null) { 
			synchronized(getLock(sessionID)) { 
				session = sessionStats.get(key); 
				if (session == null) { 
					session = new SessionRecord(key); 
					sessionStats.put(key, session); 
				}
			}
		}
		session.increaseCount(); 		
		synchronized(this) { 
			changed = true; 
		}		
	}

	public void saveStatistics(PrintStream out) { 
		try { 
			synchronized(logLock){ 
				Date now = new Date(); 
				for(SessionRecord record :  sessionStats.values()) {
					out.print(this.targetDisplayType); 
					out.print(" "); 
					out.print(serviceEndPoint); 
					out.print(" "); 
					out.print(record.getSource().getHostAddress()); 
					out.print(" "); 
					out.print(record.getSessionID()); 
					out.print(" "); 
					out.print(record.getCount()); 
					out.print(" "); 
					out.println(record.getLastUpdateTime()); 				
				}
				out.println(" "); 
				out.flush(); 
			}	
		} catch(Throwable t) { 
			t.printStackTrace(); 
		}
	}


	
	private Object getLock(String sessionID) { 
		int key = sessionID.hashCode(); 
		if (key < 0) { 
			key = -key; 
		}
		key = key % locks.length; 
		return locks[key]; 
	}

	private String getDisplayName(Class type) { 
		String className = type.getName(); 
		return className.substring("edu.ucla.cs.typecast.test.PingAck".length()); 
	}	
}
